#pragma once
#include <queue>
#include <type_traits>
#include <BWAPI.h>
#include "UnitGroup.h"

// This File controls training at a low level. It dispatches
// jobs to idling group members of the same building type. The
// priority is managed by a class inheriting from TrainingCompare
// and passing said class as a template parameter to TrainAction.
//
// Train Actions are meant to be controlled by the Training Manager
// which distrubutes jobs to the actions based on resources availible
// and parameters given by the Strategy manager.

using namespace std;

// Inherit from this class to determine the priority and reassign
// based on game needs.
class TrainingCompare
{
public:
	virtual bool operator() (PUnitType& lhs, PUnitType& rhs)=0;
};

// The following Magic will result in a type error if T does not inherit from TrainingCompare
template<class T, class Enable = void>
class TrainAction;

template <class T>
class TrainAction<T,
	typename std::enable_if< std::is_base_of< TrainingCompare, T >::value>::type>
	: public BasicAction
{
	priority_queue<PUnitType, vector<PUnitType>, T> *trainQueue;

	void _onActionStart(){}
	void _onActionStop(){Broodwar->printf("stop\n");}
	void _onFrame()
	{
		if(trainQueue->empty())
			stop();
		for ( Unitset::iterator i = group->getUnits()->begin(); i != group->getUnits()->end(); ++i ) {
			// Get a direct pointer to our unit so we do not have to continuously dereference the iterator
			Unit *u = *i;
			if ( u == NULL || !u->exists() )
				continue;

			// Ignore the unit if it has one of the following status ailments
			if ( u->isLockedDown() || u->isMaelstrommed() || u->isStasised() )
				continue;


			// Ignore the unit if it is in one of the following states
			if ( u->isLoaded() || u->isUnpowered() || u->isStuck() )
				continue;

			// Ignore the unit if it is incomplete or busy constructing
			if ( !u->isCompleted() || u->isConstructing() )
				continue;

			UnitType uType = u->getType();
			PUnitType unit = trainQueue->top();
			int minerals = Broodwar->self()->minerals();
			int gas = Broodwar->self()->gas();
			if(uType.canProduce() && !u->isTraining() && unit.mineralPrice() <= minerals && unit.gasPrice() <= gas) {
				Broodwar->printf("A %s is now training a %s", u->getType().getName().c_str(), unit.getName().c_str());
				u->train(unit);
				// There is no way to get this unit before it exists so
				// we set the Builders rally Unit to itself and we can
				// ID where the unit is from by its target when it spawns.
				// unit type and group will be added to a pqueue for the
				// BWAPI event to look at to determine the group. The priority
				// is based on the build time as they will appear at the
				// time they are dequeued + their build time. its ok to use
				// frame count because it takes more than a frame to pause
				u->setRallyPoint(u);
				struct arrival arrival;
				arrival.arrivalTime = unit.buildTime() + Broodwar->getFrameCount();
				arrival.group = unit.getGroup();
				arrival.uType = unit;
				arrival.origin = u;
				manager->addArrival(arrival);
				trainQueue->pop();
			}
		}
	}
public:
	TrainAction() : BasicAction()
	{
		trainQueue = new priority_queue<PUnitType, vector<PUnitType>, T>();
	}
	~TrainAction()
	{
		delete trainQueue;
	}
	/*TrainAction(){group = NULL;}
	TrainAction(UnitType uType, int priority = 0)
	{
		group = NULL;
		addUnitRequest(uType);
	}*/
	TrainAction<T, typename std::enable_if< std::is_base_of< TrainingCompare, T >::value>::type>* clone()
	{
		TrainAction* clone = new TrainAction(*this);
		clone->trainQueue = new priority_queue<PUnitType, vector<PUnitType>, T>(*trainQueue);
		return clone;
	}
	void addUnitRequest(PUnitType uType)
	{
		trainQueue->push(uType);
	}
};